package com.cpd.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.cpd.blog.common.constant.Constant;
import com.cpd.blog.common.constant.RedisKey;
import com.cpd.blog.common.constant.StatusCode;
import com.cpd.blog.common.utils.CheckUtil;
import com.cpd.blog.common.utils.CurrentUser;
import com.cpd.blog.common.utils.RedisUtil;
import com.cpd.blog.dto.Article.ArticleBodyVo;
import com.cpd.blog.dto.Article.ArticleRep;
import com.cpd.blog.dto.Article.ArticleReq;
import com.cpd.blog.dto.Article.TagVo;
import com.cpd.blog.dto.Page.PageParams;
import com.cpd.blog.dto.Result;
import com.cpd.blog.entity.Article;
import com.cpd.blog.entity.ArticleBody;
import com.cpd.blog.entity.ArticleTag;
import com.cpd.blog.entity.User;
import com.cpd.blog.manager.manager.ArticleManager;
import com.cpd.blog.manager.manager.CommentsManager;
import com.cpd.blog.manager.mapper.ArticleBodyMapper;
import com.cpd.blog.manager.mapper.ArticleMapper;
import com.cpd.blog.manager.mapper.ArticleTagMapper;
import com.cpd.blog.service.*;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Copydown
 * @date 2021/8/14 17:24
 */
@Service
@RequiredArgsConstructor
public class ArticleServiceImpl implements ArticleService {

    private final ArticleMapper articleMapper;

    private final TagService tagService;

    private final ArticleBodyMapper articleBodyMapper;

    private final CategoryService categoryService;

    private final ThreadService threadService;

    private final UserService userService;

    private final ArticleTagMapper articleTagMapper;

    private final RedisUtil redisUtil;

    private final ArticleManager articleManager;

    private final CommentsManager commentsManager;

    @Override
    public Result listArticle(PageParams pageParams) {
        List<Article> records = articleManager.listArticle(pageParams);
        return Result.success(copyList(records, true, true));
    }

    @Override
    public Result listUserArticle(PageParams pageParams) {
        List<Article> articles = articleManager.listUserArticle(pageParams, CurrentUser.getId());
        if (articles.size() == 0) {
            return Result.success();
        }
        return Result.success(copyList(articles, true, true));
    }

    @Override
    public Result hotArticle() {

        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.select(Article::getId, Article::getTitle, Article::getCreateTime)
                .orderByDesc(Article::getViewCounts)
                .last("limit " + 5);

        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(copyList(articles, false, false));
    }

    @Override
    public Result newArticles() {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.select(Article::getId, Article::getTitle, Article::getCreateTime)
                .orderByDesc(Article::getCreateTime)
                .last("limit " + 3);

        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(copyList(articles, false, false));
    }

    @Override
    public Result listArchives() {
        return Result.success(articleManager.listArchives());
    }

    @Override
    public Result findArticleById(Long articleId) {
        //根据 id查询文章信息
        Article article = articleMapper.selectById(articleId);

        //根据 bodyId和 categoryId 关联查询
        ArticleRep articleRep = copy(article, true, true, true, true);

        //查看完文章后阅读量+1，更新操作增加了接口的耗时，若更新时出了问题，不能影响到查看文章的操作
        //使用线程池技术把更新操作移动到线程池中异步执行，和主线程的任务错开
        threadService.updateArticleViewCount(articleMapper, article);
        return Result.success(articleRep);
    }

    @Override
    public Result publish(ArticleReq articleReq) {

        if (StringUtils.isBlank(articleReq.getTitle())) {
            return Result.fail(StatusCode.PARAMS_NULL);
        }

        // 1、文章表
        LocalDateTime now = LocalDateTime.now();
        Article article = new Article();
        article.setAuthorId(CurrentUser.getId());
        article.setCommentCounts(0);
        article.setSummary(articleReq.getSummary());
        article.setTitle(articleReq.getTitle());
        article.setViewCounts(Constant.ONE_LONG);
        article.setWeight(Constant.ARTICLE_NEW);
        article.setBodyId(-1L);
        article.setCategoryId(Long.parseLong(articleReq.getCategory().getId()));
        article.setCreateTime(now);
        article.setModifyTime(now);
        article.setDeleted(false);
        article.setVersion(0L);
        articleMapper.insert(article);

        // 2、文章标签表
        insertTagList(articleReq, article.getId());

        // 3、文章内容表
        ArticleBody articleBody = new ArticleBody();
        articleBody.setArticleId(article.getId());
        articleBody.setContent(articleReq.getBody().getContent());
        articleBody.setContentHtml(articleReq.getBody().getContentHtml());
        articleBody.setDeleted(false);
        articleBodyMapper.insert(articleBody);

        // 绑定文章和文章内容 id
        article.setBodyId(articleBody.getId());
        articleMapper.updateById(article);

        ArticleRep articleRep = new ArticleRep();
        articleRep.setId(String.valueOf(article.getId()));

        // 删除最新文章缓存
        redisUtil.checkAndDelete(RedisKey.NEW_ARTICLE);

        return Result.success(articleRep);
    }

    @Override
    public Result edit(ArticleReq articleReq) {

        if (CheckUtil.isNull(articleReq.getId())) {
            return Result.fail(StatusCode.ARTICLE_ID_NULL);
        }

        if (StringUtils.isBlank(articleReq.getTitle())) {
            return Result.fail(StatusCode.PARAMS_NULL);
        }
        Article article = new Article();
        article.setId(articleReq.getId());
        article.setSummary(articleReq.getSummary());
        article.setTitle(articleReq.getTitle());
        ArticleBody articleBody = new ArticleBody();
        articleBody.setContent(articleReq.getBody().getContent());
        articleBody.setContentHtml(articleReq.getBody().getContentHtml());
        List<TagVo> tagList = articleReq.getTags();

        ArticleRep articleRep = articleManager.updateArticleWithRelate(article, articleBody, tagList);

        redisUtil.checkAndDelete(RedisKey.NEW_ARTICLE);
        return Result.success(articleRep);
    }

    private void insertTagList(ArticleReq articleReq, Long articleId) {
        List<TagVo> tags = articleReq.getTags();
        if (tags != null) {
            for (TagVo tag : tags) {
                ArticleTag articleTag = new ArticleTag();
                articleTag.setTagId(Long.parseLong(tag.getId()));
                articleTag.setArticleId(articleId);
                articleTag.setDeleted(false);
                articleTagMapper.insert(articleTag);
            }
        }
    }

    @Override
    public boolean commitIncr(Long articleId) {
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Article::getId, articleId);
        Article article = articleMapper.selectOne(queryWrapper);
        article.setCommentCounts(article.getCommentCounts() + Constant.ONE_INT);

        UpdateWrapper<Article> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(Article::getId, articleId)
                .setEntity(article);
        if (articleMapper.update(article, updateWrapper) == Constant.ONE_INT) {
            return Constant.BOOLEAN_TRUE;
        }
        return Constant.BOOLEAN_FALSE;
    }

    @Override
    public Result deleteWithComment(Long articleId) {
        if (CheckUtil.isNull(articleId)) {
            return Result.fail(StatusCode.PARAMS_ERROR);
        }
        Article article = articleManager.findArticleById(articleId);
        if (CheckUtil.isNull(article)) {
            return Result.fail(StatusCode.ARTICLE_NOT_EXISTED);
        }
        if (CurrentUser.noEqualsId(article.getAuthorId())) {
            return Result.fail(StatusCode.USER_NOT_HAVE_DATA);
        }
        if (CheckUtil.isFalse(articleManager.deleteArticleRelateById(articleId))) {
            return Result.fail(StatusCode.ARTICLE_RELATE_DELETE_FAILED);
        }
        if (CheckUtil.isFalse(commentsManager.deleteCommentsByArticleId(articleId))) {
            return Result.fail(StatusCode.COMMENT_DELETE_FAILED);
        }
        redisUtil.delete(RedisKey.NEW_ARTICLE);
        redisUtil.delete(RedisKey.HOT_ARTICLE);
        return Result.success(StatusCode.ARTICLE_DELETE_SUCCESS);
    }


    private List<ArticleRep> copyList(List<Article> records, boolean isTag, boolean isAuthor) {

        List<ArticleRep> articleRepList = new ArrayList<>();
        for (Article record : records) {
            articleRepList.add(copy(record, isTag, isAuthor, false, false));
        }
        return articleRepList;
    }

    private List<ArticleRep> copyList(List<Article> records, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {

        List<ArticleRep> articleRepList = new ArrayList<>();
        for (Article record : records) {
            articleRepList.add(copy(record, isTag, isAuthor, isBody, isCategory));
        }
        return articleRepList;
    }

    private ArticleRep copy(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory) {

        ArticleRep articleRep = new ArticleRep();
        articleRep.setId(String.valueOf(article.getId()));
        BeanUtils.copyProperties(article, articleRep);

        //"yyyy-MM-dd HH:mm"
        articleRep.setCreateDate(article.getCreateTime().format(DateTimeFormatter.ofPattern(Constant.DATE_TIME_FORMAT)));
        //并不是所有接口都需要表标签、作者信息
        if (isTag) {
            Long articleId = article.getId();
            articleRep.setTags(tagService.findTagsByArticleId(articleId));
        }
        if (isAuthor) {
            Long authorId = article.getAuthorId();
            User user = userService.findUserById(authorId);
            articleRep.setAuthor(user.getNickname());
            articleRep.setAuthorAvatar(user.getAvatar());
            articleRep.setAuthorId(String.valueOf(user.getId()));
        }

        if (isBody) {
            Long bodyId = article.getBodyId();
            articleRep.setBody(findArticleBodyById(bodyId));
        }
        if (isCategory) {
            Long categoryId = article.getCategoryId();
            articleRep.setCategory(categoryService.findCategoryById(categoryId));
        }

        return articleRep;
    }

    private ArticleBodyVo findArticleBodyById(Long bodyId) {

        ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
        ArticleBodyVo articleBodyVo = new ArticleBodyVo();
        articleBodyVo.setContent(articleBody.getContent());

        return articleBodyVo;
    }

}
